
#type vertex
#version 330 core

layout (location = 0) in vec3 Positions;
layout (location = 1) in vec3 Normals;
layout (location = 2) in vec2 TexCoords;

uniform mat4 mvp;
uniform mat4 light_mvp;

out vec3 fragPos;
out vec3 normal;
out vec2 texCoord;
out vec3 lightSpaceScreenPos;

void main()
{
	fragPos = Positions;
	normal = normalize(Normals);
	texCoord = TexCoords;

	gl_Position = mvp * vec4(Positions, 1.0f);
	vec4 lightSpaceFragPos = light_mvp * vec4(Positions, 1.0f);
	lightSpaceScreenPos = (lightSpaceFragPos.xyz / lightSpaceFragPos.w) * 0.5f + 0.5f;
}



#type fragment
#version 330 core

#define EPS 0.003
#define PI  3.141592653589793
#define PI2 6.283185307179586

in vec3 fragPos;
in vec3 normal;
in vec2 texCoord;
in vec3 lightSpaceScreenPos;

uniform vec3 lightPos;
uniform vec3 cameraPos;
uniform vec3 lightIntensity;

uniform vec3 Ka;  	// Ambient Color
uniform vec3 Kd;  	// Diffuse Color
uniform vec3 Ks;  	// Specular Color
uniform float Ns;  	// Specular Exponent
uniform float Ni;  	// Optical Density
uniform float d;  	// Dissolve
uniform int illum;  // Illumination

uniform int sample_map_Ka;
uniform int sample_map_Kd;
uniform int sample_map_Ks;
uniform int sample_map_Ns;
uniform int sample_map_d;
uniform int sample_map_bump;

uniform sampler2D shadowMap;
uniform int shadowMapSize;

uniform sampler2D map_Ka;  	// Ambient Texture Map
uniform sampler2D map_Kd;  	// Diffuse Texture Map
uniform sampler2D map_Ks;  	// Specular Texture Map
uniform sampler2D map_Ns;  	// Specular Hightlight Map
uniform sampler2D map_d;  	// Alpha Texture Map
uniform sampler2D map_bump;	// Bump / Normal Map


float rand_minus_one_to_one(const float x ) { 
  // -1 ~ 1
  return fract(sin(x)*10000.0);
}

float rand_zero_to_one(const vec2 uv) { 
	// 0 ~ 1
	const float a = 12.9898, b = 78.233, c = 43758.5453;
	highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );
	return fract(sin(sn) * c);
}

#define NUM_SAMPLES 100
#define BLOCKER_SEARCH_NUM_SAMPLES NUM_SAMPLES
#define PCF_NUM_SAMPLES NUM_SAMPLES
#define NUM_RINGS 10

vec2 poissonDisk[NUM_SAMPLES];

void poissonDiskSamples() {
	float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES );
	float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES );

	float angle = rand_zero_to_one(lightSpaceScreenPos.xy) * PI2;
	float radius = INV_NUM_SAMPLES;
	float radiusStep = radius;

	for( int i = 0; i < NUM_SAMPLES; i ++ ) {
		poissonDisk[i] = vec2( cos( angle ), sin( angle ) ) * pow( radius, 0.75 );
		radius += radiusStep;
		angle += ANGLE_STEP;
	}
}

void uniformDiskSamples() {
	float randNum = rand_zero_to_one(lightSpaceScreenPos.xy);
	float sampleX = rand_minus_one_to_one( randNum ) ;
	float sampleY = rand_minus_one_to_one( sampleX ) ;

	float angle = sampleX * PI2;
	float radius = sqrt(sampleY);

	for( int i = 0; i < NUM_SAMPLES; i ++ ) {
		poissonDisk[i] = vec2( radius * cos(angle) , radius * sin(angle)  );

		sampleX = rand_minus_one_to_one( sampleY ) ;
		sampleY = rand_minus_one_to_one( sampleX ) ;

		angle = sampleX * PI2;
		radius = sqrt(sampleY);
	}
}

float PCF() {
	poissonDiskSamples();

	float visibility = 0.f;
	float currentDepth = lightSpaceScreenPos.z;
	float filterStride = 10.f;
	float filterRange = filterStride / shadowMapSize;

	for (int i = 0; i < NUM_SAMPLES	; ++i) {
		float shadowDepth = texture2D(shadowMap, poissonDisk[i] * filterRange + lightSpaceScreenPos.xy).r;
		if (currentDepth < shadowDepth + EPS)
			visibility += 1.f / NUM_SAMPLES;
	}
	return visibility;
}

float getBlockerAverageDepth() {
	poissonDiskSamples();

	float allBlockerDepth = 0.f;
	float blockerNum = 0.f;
	float currentDepth = lightSpaceScreenPos.z;
	float filterStride = 10.f;
	float filterRange = filterStride / shadowMapSize;

	for (int i = 0; i < NUM_SAMPLES	; ++i) {
		float shadowDepth = texture2D(shadowMap, poissonDisk[i] * filterRange + lightSpaceScreenPos.xy).r;
		if (currentDepth > shadowDepth + EPS) {  // Find the blocker.
			++blockerNum;
			allBlockerDepth += shadowDepth;
		}
	}

	if (0.f == blockerNum)
		return 1.f;
	else
		return allBlockerDepth / blockerNum;
}

float PCSS() {
	// 1.Get the average depth of the blocker.
	float dBlocker = getBlockerAverageDepth();
	float dReceiver = lightSpaceScreenPos.z;
	float wLight = 1.f;

	// 2.Compute penumbra.
	float wPenumbra = wLight * (dReceiver - dBlocker) / dBlocker;

	// 3.PCF.
	float visibility = 0.f;
	float currentDepth = lightSpaceScreenPos.z;
	float filterStride = 10.f;
	float filterRange = filterStride / shadowMapSize;

	for (int i = 0; i < NUM_SAMPLES	; ++i) {
		float shadowDepth = texture2D(shadowMap, poissonDisk[i] * filterRange * wPenumbra + lightSpaceScreenPos.xy).r;
		if (currentDepth < shadowDepth + EPS)
			visibility += 1.f / NUM_SAMPLES;
	}
	return visibility;
}

float solidShadow() {
	float currentDepth = lightSpaceScreenPos.z;
	float shadowDepth = texture2D(shadowMap, lightSpaceScreenPos.xy).r;
	float bias = max(EPS, EPS * (1 - dot(normal, normalize(lightPos - fragPos))));
	return currentDepth < shadowDepth + bias ? 1.f : 0.f;
}

vec4 phongShading() {
	vec3 ambientColor;
	if (1 == sample_map_Ka)
		ambientColor = texture2D(map_Ka, texCoord).rgb;
	else
		ambientColor = Ka;

	vec3 diffuseColor;
	if (1 == sample_map_Kd)
		diffuseColor = texture2D(map_Kd, texCoord).rgb;
	else
		diffuseColor = Kd;

	vec3 specularColor;
	if (1 == sample_map_Ks)
		specularColor = texture2D(map_Ks, texCoord).rgb;
	else
		specularColor = Ks;
	
	vec3 lightDir = normalize(lightPos - fragPos);
	float diff = max(dot(lightDir, normal), 0.f);
	vec3 light_atten_coff = lightIntensity / length(lightPos - fragPos);
	vec3 diffuse =  diff * light_atten_coff * diffuseColor;

	vec3 viewDir = normalize(cameraPos - fragPos);
	float spec = pow(max(dot(normal, normalize(viewDir + lightDir)), 0.f), Ns);
	vec3 specular = specularColor * light_atten_coff * spec;

	return vec4(ambientColor + (diffuse + specular) * PCSS(), 1.f);
}

void main()
{
	gl_FragColor = phongShading();
}
